/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.acworks.acroute.v2;

import com.acworks.acroute.v2.impl.NodeImpl2;
import com.acworks.acroute.v2.impl.ArcImpl;
import com.acworks.acroute.v2.impl.GraphUtil;
import com.acworks.acroute.v2.impl.ImmutableGraphImpl;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;

/**
 *
 * @author nikita
 */
public class GraphFactory {

    private GraphFactory() {
    }

    public static ImmutableGraph createImmutableGraph(int width, int height, List<Integer> inputIntegers) {
        assert width > 0;
        assert height > 0;
        assert inputIntegers != null;

        if (inputIntegers.size() != width * height) {
            throw new IllegalArgumentException("invalid input: number of inputIntegers incongruent with width/height");
        }

        final SortedSet<Node> graphNodes = new TreeSet<Node>();

        int nodeId = 0;
        Set<Integer> assignedNodeIDs = new HashSet<Integer>();

        NodeImpl2 sn = null;
        NodeImpl2 en = null;

        //construct participating nodes
        for (Integer inputInt : inputIntegers) {

            NodeImpl2 node = null;
            switch (inputInt) {
                case 1:
                    //ignore foreign room
                    break;
                case 0:
                    node = new NodeImpl2(nodeId, NodeType.ROOM);
                    break;
                case 2:
                    if (sn != null) {
                        throw new IllegalArgumentException("multiple starting nodes detected: " + inputInt);
                    }
                    sn = new NodeImpl2(nodeId, NodeType.STARTING);
                    node = sn;
                    break;
                case 3:
                    if (en != null) {
                        throw new IllegalArgumentException("multiple ending nodes detected: " + inputInt);
                    }
                    en = new NodeImpl2(nodeId, NodeType.ENDING);
                    node = en;
                    break;
                default:
                    throw new IllegalArgumentException("invalid input integer: " + inputInt);
            }

            if (node != null) {
                assignedNodeIDs.add(node.getId());
                graphNodes.add(node);
            }

            nodeId++;
        }

        assert nodeId == inputIntegers.size();

        //construct participating arcs
        Set<Arc> arcs = new HashSet<Arc>();
        for (Integer id : assignedNodeIDs) {

            Node n = GraphUtil.findById(graphNodes, id);
            assert n != null;

            //compute ids of 4 potential neighbors
            List<Integer> neighborIDs = new ArrayList<Integer>(4);

            //above
            neighborIDs.add(id - width);

            //right of
            neighborIDs.add((id + 1) % width != 0 ? id + 1 : -1);

            //below
            neighborIDs.add(id + width);

            //left of
            neighborIDs.add(id % width != 0 ? id - 1 : -1);

            for (Integer neighborID : neighborIDs) {
                Node neighbor = GraphUtil.findById(graphNodes, neighborID);
                if (neighbor != null && (GraphUtil.findConnectingArc(n, neighbor, arcs) == null)) {
                    //create arc between n and 'neighbor'
                    arcs.add(createArc(n, neighbor));
                }
            }

        }

        //check if a root node need to be created
        if (GraphUtil.findConnectingArc(sn, en, arcs) == null) {
            NodeImpl2 rootNode = new NodeImpl2(inputIntegers.size(), NodeType.ROOT); //should its' id be '0'?
            graphNodes.add(rootNode);
            arcs.add(new ArcImpl(rootNode, sn, ArcDirectionType.DIRECTED));
            arcs.add(new ArcImpl(en, rootNode, ArcDirectionType.DIRECTED));
        }

        //update successors
        for (Arc arc : arcs) {
            arc.getSourceNode().getSuccessors().add(arc.getTargetNode());
            if (arc.getType() == ArcDirectionType.UNDIRECTED) {
                arc.getTargetNode().getSuccessors().add(arc.getSourceNode());
            }
        }

        ImmutableGraph graph = new ImmutableGraphImpl(graphNodes, arcs);
        return graph;
    }

    static private Arc createArc(Node n1, Node n2) {
        assert n1 != null;
        assert n2 != null;
        assert !n1.equals(n2);

        final Arc arc;

        //if both nodes are '0' -> create undirected arc
        //if one is StartingNode or EndingNode, create a directed arc

        if (n1.getType() == NodeType.STARTING || n2.getType() == NodeType.ENDING) {
            //directed from n1 to n2
            arc = new ArcImpl(n1, n2, ArcDirectionType.DIRECTED);
        } else if (n1.getType() == NodeType.ENDING || n2.getType() == NodeType.STARTING) {
            //directed from n2 to n1
            arc = new ArcImpl(n2, n1, ArcDirectionType.DIRECTED);
        } else {
            //undirected
            arc = new ArcImpl(n1, n2, ArcDirectionType.UNDIRECTED);
        }

        return arc;
    }
}
